package nl.nhl.idp.Views.Charts;

import java.util.HashMap;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

/**
 * Chart used to display one or multiple datasets in a scrollable and zoomable graph.
 * @author Alex Wijnbeek
 *
 */
public class SensorChart extends View
{
	//### CONSTANTS ###
		//defines how sensitive the reaction to dragging is 
		private final float DRAG_SENSITIVITY = 0.002f;
		
		//major and minor grid scales
		//(It is advised to make the major scale a multiple of the minor scale)
		private final int DEFAULT_SCALE_MINOR_X = 5;
		private final int DEFAULT_SCALE_MAJOR_X = 25;
		private final int DEFAULT_SCALE_MINOR_Y = 10;
		private final int DEFAULT_SCALE_MAJOR_Y = 50;
		
		//default size for drawing text
		private final int DEFAULT_FONTSIZE = 22;
		
		//graph ranges, define how far the user can zoom in and out
		private final float DEFAULT_GRAPHRANGE = 30; //default x range
		private final float GRAPH_MIN_RANGE = 5; //minimum x range when zoomed in
		private final float GRAPH_MAX_RANGE = 120; //maximum x range when zoomed out
	//### END CONSTANTS ###	
	
	//touch parameters
	private boolean touchDown = false;
	private boolean frozen = false;
	private float frozenMaxX;
	private float freezeX;
	private float freezeY;
	private float drawMaxX;
	
	//datasets
	private HashMap<String, DataSet> dataSets;
	
	//paints
	private Paint gridMajorPaint;
	private Paint gridMinorPaint;
	private Paint textPaintAlignLeft;
	private Paint textPaintAlignCenter;
	private Paint textPaintAlignRight;

	//graph parameters
	private int fontSize = DEFAULT_FONTSIZE;
	/**
	 * Gets the grid value font size
	 * @return
	 */
	public int GetFontSize(){return fontSize;}
	/**
	 * Sets the grid value font size
	 * @param fontSize
	 */
	public void SetFontSize(int fontSize){this.fontSize = fontSize;}
	
	private float graphRange = DEFAULT_GRAPHRANGE;
	/**
	 * Gets the X range shown when the chart is first displayed
	 */
	public float GetGraphRange(){return graphRange;}
	/**
	 * Sets the X range shown when the chart is first displayed
	 * @param graphRange
	 */
	public void SetGraphRange(float graphRange){this.graphRange = graphRange;}
	
	private float scaleMinorX = DEFAULT_SCALE_MINOR_X;
	/**
	 * Gets the minor grid scale along the X axis
	 * @return
	 */
	public float GetScaleMinorX(){return scaleMinorX;}
	/**
	 * Sets the minor grid scale along the X axis
	 * @param scaleMinorX
	 */
	public void SetScaleMinorX(float scaleMinorX){this.scaleMinorX = scaleMinorX;}
	
	private float scaleMajorX = DEFAULT_SCALE_MAJOR_X;
	/**
	 * Gets the major grid scale along the X axis
	 * @return
	 */
	public float GetScaleMajorrX(){return scaleMajorX;}
	/**
	 * Sets the major grid scale along the X axis
	 * @param scaleMajorX
	 */
	public void SetScaleMajorX(float scaleMajorX){this.scaleMajorX = scaleMajorX;}
	
	private float scaleMinorY = DEFAULT_SCALE_MINOR_Y;
	/**
	 * Gets the minor grid scale along the Y axis
	 * @return
	 */
	public float GetScaleMinorY(){return scaleMinorY;}
	/**
	 * Sets the minor grid scale along the Y axis
	 * @param scaleMinorY
	 */
	public void SetScaleMinorY(float scaleMinorY){this.scaleMinorY = scaleMinorY;}
	
	private float scaleMajorY = DEFAULT_SCALE_MAJOR_Y;
	/**
	 * Gets the major grid scale along the Y axis
	 * @return
	 */
	public float GetScaleMajorY(){return scaleMajorY;}
	/**
	 * Sets the major grid scale along the Y axis
	 * @param scaleMajorY
	 */
	public void SetScaleMajorY(float scaleMajorY){this.scaleMajorY = scaleMajorY;}
	
	float maxX, maxY, minX, minY;
	/**
	 * Gets the maximum visible X coordinate in the current view
	 * @return
	 */
	public float GetMaxX(){return drawMaxX;}
	/**
	 * Gets the maximum visible Y coordinate in the current view
	 * @return
	 */
	public float GetMaxY(){return maxY;}
	/**
	 * Gets the minimum visible X coordinate in the current view
	 * @return
	 */
	public float GetMinX(){return minX;}
	/**
	 * Gets the minimum visible Y coordinate in the current view
	 * @return
	 */
	public float GetMinY(){return minY;}
	
	/**
	 * Sets a new dataset with the given name. If a dataset with this name already exists, it is replaced.
	 * @param name The Name of the Dataset
	 * @param data The DataSet to add
	 */
	public void SetData(String name, DataSet data)
	{
		dataSets.put(name, data);
		invalidate();
	}
	
	/**
	 * Creates a new SensorChart
	 * @param context
	 * @param attrs
	 * @param defStyle
	 */
	public SensorChart(Context context, AttributeSet attrs, int defStyle)
	{
		super(context, attrs, defStyle);
		
		dataSets = new HashMap<String, DataSet>();
		
		gridMajorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
		gridMajorPaint.setStrokeWidth(2);
		gridMajorPaint.setColor(Color.GRAY);
		gridMajorPaint.setStyle(Style.STROKE);
		
		gridMinorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
		gridMinorPaint.setStrokeWidth(1);
		gridMinorPaint.setColor(Color.DKGRAY);
		gridMinorPaint.setStyle(Style.STROKE);
		
		textPaintAlignCenter = new Paint(Paint.ANTI_ALIAS_FLAG);
		textPaintAlignCenter.setColor(Color.LTGRAY);
		textPaintAlignCenter.setTextAlign(Align.CENTER);
		
		textPaintAlignLeft = new Paint(Paint.ANTI_ALIAS_FLAG);
		textPaintAlignLeft.setColor(Color.LTGRAY);
		textPaintAlignLeft.setTextAlign(Align.LEFT);
		
		textPaintAlignRight = new Paint(Paint.ANTI_ALIAS_FLAG);
		textPaintAlignRight.setColor(Color.LTGRAY);
		textPaintAlignRight.setTextAlign(Align.RIGHT);
	}
	
	/**
	 * Creates a new SensorChart
	 * @param context
	 * @param attrs
	 */
	public SensorChart(Context context, AttributeSet attrs)
	{
		this(context, attrs, 0);
	}
	
	/**
	 * Creates a new SensorChart
	 * @param context
	 */
	public SensorChart(Context context)
	{
		this(context, null);
	}
	
	/**
	 * Resets the current zoom and drag to their default states
	 */
	public void Reset()
	{
		frozen = false;
		graphRange = DEFAULT_GRAPHRANGE;
	}
	
	/**
	 * Update minimum and maximum bounds of the graph
	 */
	private void CheckMinMax()
	{
		//reset bounds to opposite values
		maxX = Integer.MIN_VALUE;
		maxY = Integer.MIN_VALUE;
		minX = Integer.MAX_VALUE;
		minY = Integer.MAX_VALUE;
		
		//test for all datasets
		for(int d=0;d<dataSets.size();d++)
		{
			//get a dataset
			DataSet data = (DataSet)dataSets.values().toArray()[d];
			for(int i = 0;i<data.GetDataCount();i++)
			{
				//if the datapoint's X coordinate is higher than the minimum drawn X 
				if(data.GetPointAt(i).GetX() > drawMaxX-graphRange)
				{
					//if the new X is higher than maxX, make it the new maxX
					maxX = data.GetPointAt(i).GetX() > maxX ? data.GetPointAt(i).GetX() : maxX;
					
					//if the datapoint is lower than the maximum shown X
					if(data.GetPointAt(i).GetX() < drawMaxX)
					{
						maxY = data.GetPointAt(i).GetY() > maxY ? data.GetPointAt(i).GetY() : maxY;
						minY = data.GetPointAt(i).GetY() < minY ? data.GetPointAt(i).GetY() : minY;
					}
				}
			}
		}
		if(frozen)
		{
			drawMaxX = frozenMaxX;
		}
		else
		{
			drawMaxX = maxX;
		}
		
		//minX is defined by maxX and graphrange
		minX = drawMaxX-graphRange;
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent event)
	{
		switch(event.getAction())
		{
			case MotionEvent.ACTION_DOWN:
			{
				touchDown = true;
				
				//if already frozen, we stick with the original starting point
				if(!frozen)
					frozenMaxX = maxX;
				
				//set the graph to frozen
				frozen = true;
				freezeX = event.getX();
				freezeY = event.getY();
				
			}break;
			
			case MotionEvent.ACTION_MOVE:
			{
				//scroll the graph by left/right movement
				frozenMaxX += (freezeX-event.getX())*(graphRange*0.001f);
				freezeX = event.getX();
				
				//adjust the graphRange by up/down movement
				if((graphRange > GRAPH_MIN_RANGE || freezeY>event.getY())&&(graphRange < GRAPH_MAX_RANGE || freezeY<event.getY()))
				{
					graphRange += (freezeY-event.getY())*(graphRange*DRAG_SENSITIVITY);
				}
				freezeY = event.getY();
				
				
			}break;
			
			case MotionEvent.ACTION_UP:
			{
				touchDown = false;
				
				//if we moved the graph beyond the current maxX, start tracking the new data again
				if(frozenMaxX > maxX)
					frozen = false;
				
			}break;
		}
		
		invalidate();
		
		return true;
	}
	
	@Override
	protected void onDraw(Canvas canvas)
	{
		super.onDraw(canvas);
		
		int height = getHeight();
		int width = getWidth();
		
		if(dataSets != null && height != 0)
		{
			//test for min and max values
			CheckMinMax();
			
			//draw vertical gridlines
			int start = (int)((minX%scaleMinorX));
			for(int i=(int)(minX-scaleMinorX);i<(drawMaxX+scaleMinorX);i+=scaleMinorX)
			{
				float x = ((i-start)-minX)/(drawMaxX-minX)*width;
				if((i-start)%scaleMajorX == 0)
				{
					//draw major gridline
					canvas.drawLine(x, 0, x, height, gridMajorPaint);
				}
				else
				{
					//draw minor gridline
					canvas.drawLine(x, 0, x, height, gridMinorPaint);
				}
				//draw grid value on bottom
				canvas.drawText((i-start)+"", x, height, textPaintAlignCenter);
				
				//draw grid value on top
				canvas.drawText((i-start)+"", x, 12, textPaintAlignCenter);
			}

			//draw horizontal gridlines
			start = (int)((minY%scaleMinorY));
			for(int i=(int)(minY-scaleMinorY);i<(maxY+scaleMinorY);i+=scaleMinorY)
			{
				float y = height-(((i+start)-minY)/(maxY-minY)*height);
				
				if((i+start)%scaleMajorY==0)
				{
					//draw major gridline
					canvas.drawLine(0, y, width, y, gridMajorPaint);
				}
				else
				{
					//draw minor gridline
					canvas.drawLine(0, y, width, y, gridMinorPaint);
				}
				
				//draw grid value on left
				canvas.drawText((i+start)+"", 0, y, textPaintAlignLeft);
				
				//draw grid value on right
				canvas.drawText((i+start)+"", width, y, textPaintAlignRight);
			}
			
			//draw datasets
			for(int d=0;d<dataSets.size();d++)
			{
				((DataSet)dataSets.values().toArray()[d]).Draw(this, canvas, d);
			}
			
			//draw graph ranges when dragging
			if(touchDown)
			{
				canvas.drawText("X:["+(int)minX+","+(int)drawMaxX+"] Y:["+(int)minY+","+(int)maxY+"]", freezeX, freezeY - 50,
						textPaintAlignCenter);
			}
		}
	}
}
